//===================================================================================
// Microsoft patterns & practices
// Composite Application Guidance for Windows Presentation Foundation and Silverlight
//===================================================================================
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===================================================================================
using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Practices.Prism.Events
{
	///<summary>
	/// Defines a base class to publish and subscribe to events.
	///</summary>
	public abstract class EventBase
	{
		private readonly List<IEventSubscription> _subscriptions = new List<IEventSubscription>();

		/// <summary>
		/// Gets the list of current subscriptions.
		/// </summary>
		/// <value>The current subscribers.</value>
		protected ICollection<IEventSubscription> Subscriptions
		{
			get { return _subscriptions; }
		}

		/// <summary>
		/// Adds the specified <see cref="IEventSubscription"/> to the subscribers' collection.
		/// </summary>
		/// <param name="eventSubscription">The subscriber.</param>
		/// <returns>The <see cref="SubscriptionToken"/> that uniquely identifies every subscriber.</returns>
		/// <remarks>
		/// Adds the subscription to the internal list and assigns it a new <see cref="SubscriptionToken"/>.
		/// </remarks>
		protected virtual SubscriptionToken InternalSubscribe(IEventSubscription eventSubscription)
		{
			if (eventSubscription == null) throw new System.ArgumentNullException("eventSubscription");

			eventSubscription.SubscriptionToken = new SubscriptionToken(Unsubscribe);

			lock (Subscriptions)
			{
				Subscriptions.Add(eventSubscription);
			}
			return eventSubscription.SubscriptionToken;
		}

		/// <summary>
		/// Calls all the execution strategies exposed by the list of <see cref="IEventSubscription"/>.
		/// </summary>
		/// <param name="arguments">The arguments that will be passed to the listeners.</param>
		/// <remarks>Before executing the strategies, this class will prune all the subscribers from the
		/// list that return a <see langword="null" /> <see cref="Action{T}"/> when calling the
		/// <see cref="IEventSubscription.GetExecutionStrategy"/> method.</remarks>
		protected virtual void InternalPublish(params object[] arguments)
		{
			List<Action<object[]>> executionStrategies = PruneAndReturnStrategies();
			foreach (var executionStrategy in executionStrategies)
			{
				executionStrategy(arguments);
			}
		}

		/// <summary>
		/// Removes the subscriber matching the <seealso cref="SubscriptionToken"/>.
		/// </summary>
		/// <param name="token">The <see cref="SubscriptionToken"/> returned by <see cref="EventBase"/> while subscribing to the event.</param>
		public virtual void Unsubscribe(SubscriptionToken token)
		{
			lock (Subscriptions)
			{
				IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
				if (subscription != null)
				{
					Subscriptions.Remove(subscription);
				}
			}
		}

		/// <summary>
		/// Returns <see langword="true"/> if there is a subscriber matching <see cref="SubscriptionToken"/>.
		/// </summary>
		/// <param name="token">The <see cref="SubscriptionToken"/> returned by <see cref="EventBase"/> while subscribing to the event.</param>
		/// <returns><see langword="true"/> if there is a <see cref="SubscriptionToken"/> that matches; otherwise <see langword="false"/>.</returns>
		public virtual bool Contains(SubscriptionToken token)
		{
			lock (Subscriptions)
			{
				IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
				return subscription != null;
			}
		}

		private List<Action<object[]>> PruneAndReturnStrategies()
		{
			List<Action<object[]>> returnList = new List<Action<object[]>>();

			lock (Subscriptions)
			{
				for (var i = Subscriptions.Count - 1; i >= 0; i--)
				{
					Action<object[]> listItem =
						_subscriptions[i].GetExecutionStrategy();

					if (listItem == null)
					{
						// Prune from main list. Log?
						_subscriptions.RemoveAt(i);
					}
					else
					{
						returnList.Add(listItem);
					}
				}
			}

			return returnList;
		}
	}
}